因為正在開發的系統是內部類型,希望只是簡單建立server-side的驗證碼機制就好,所以就不考慮使用Google reCaptcha。網路上.NET Core文章有點亂,版本又雜(微軟根本版本之鬼...),順手整理一下
.NET 版本: .NET Core 5
.NET Core的所有物件皆須已注入的方式使用,而我們這次要將驗證碼儲存在Session
中。
需要先在startup.cs
中增加以下項目:
app.UseSession()
,告訴.NET Core需要使用Sessionservices.AddDistributedMemoryCache()
,注入分散式記憶體快取物件,Session會用到services.AddSession()
,注入Seesionservices.AddHttpContextAccessor()
,後面會說明為什麼有這行。startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 注入分散式記憶體快取
services.AddDistributedMemoryCache();
// 注入Session
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(10);//You can set Time
});
// 注入 HttpContextAccessor
services.AddHttpContextAccessor();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestValidation", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TestValidation v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
// 使用Session
app.UseSession();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
接下來我們建立一個.NET Core 類別庫專案,並建立CodeValidation.cs
,interface
、class
放在同一個檔案,方便使用。
這裡要注意一點,.NET Core很多東西是要自己額外安裝:
IHttpContextAccessor
需要從nuget
安裝套件: Microsoft.AspNetCore.Http
public interface ICodeValidator
{
/// <summary>
/// 驗證
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
bool Validate(string code);
/// <summary>
/// 產生驗證碼
/// </summary>
/// <returns></returns>
string Generate();
}
public class CodeValidator : ICodeValidator
{
private const string KEY = "ValidationCode";
private HttpContext _httpContext { get; set; }
// 注入 IHttpContextAccessor ,因為我們要使用HttpContext取得Session
public CodeValidator(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public string Generate()
{
string code = CreateRandomWord(5);
// session只能儲存byte[],將字串轉為byte[]
byte[] codeBytes = Encoding.ASCII.GetBytes(code);
_httpContext.Session.Set(KEY, codeBytes);
return code;
}
public bool Validate(string code)
{
bool isOk = false;
byte[] codeBytes = null;
if(_httpContext.Session.TryGetValue(KEY,out codeBytes))
{
// 從Session取出來的byte[] 轉成字串
string serverCode = Encoding.ASCII.GetString(codeBytes);
// 忽略大小寫比對
if (serverCode.Equals(code, StringComparison.InvariantCultureIgnoreCase))
{
isOk = true;
}
}
// 無論成功失敗,都清除Session。(依情境,非必要)
_httpContext.Session.Remove(KEY);
return isOk;
}
/// <summary>
/// 產生隨機字串
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
private string CreateRandomWord(int length = 5)
{
string code = "";
var letters = "ABCDEFGHJKMPQRSTUVWXYZ23456789abcdefghjkmpqrstuvwxyz".ToArray();
Random r = new Random();
for (int i = 0; i < length; i++)
{
int index = r.Next(0, letters.Length);
code += letters[index];
}
return code;
}
}
這就是為什麼我們會注入HttpContextAccessor
的原因,因為實際專案基本上都是分層架構,所以要在不同層級取得Session,就必須注入此物件,而不是從Controller傳入HttpContext。
接下來我們需要將CodeValidator
注入給Controller使用,所以startup.cs
要增加程式
public void ConfigureServices(IServiceCollection services)
{
// 注入分散式記憶體快取
services.AddDistributedMemoryCache();
// 注入Session
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(10);//You can set Time
});
// 注入驗證物件
services.AddScoped(typeof(ICodeValidator), typeof(CodeValidator));
// 注入 HttpContextAccessor
services.AddHttpContextAccessor();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestValidation", Version = "v1" });
});
}
建立CodeController
製作驗證碼API。
[Route("api/[controller]")]
[ApiController]
public class CodeController : ControllerBase
{
private ICodeValidator _codeValidator { get; set; }
public CodeController(ICodeValidator codeValidator)
{
_codeValidator = codeValidator;
}
[HttpGet]
public ActionResult<string> Generate()
{
string code = _codeValidator.Generate();
return Ok(code);
}
[HttpGet("{code}")]
public ActionResult Validate(string code)
{
bool isOk = _codeValidator.Validate(code);
return isOk ? Ok() : BadRequest();
}
}
測試結果
取得驗證碼,反回CDXPM
輸入驗證碼驗證,返回200 OK
輸入錯誤測試
從新取得驗證碼 psBw9
輸入錯誤的驗證碼12345
,返回400 BadRequest